function queue(fn, limit, trigger, flushTrigger, toggleTrigger) {
    var argQueues = [];
    var context = null;
    var availableCount = limit;
    var isWaiting = false;
    var permitAll = false;

    function runQueue() {
        if (!argQueues.length) return;
        if (availableCount > 0 || permitAll) {
            var runNum = permitAll
                ? argQueues.length
                : Math.min(availableCount, argQueues.length);
            var argsToRun = argQueues.splice(0, runNum);
            argsToRun.forEach((item) => {
                fn.apply(context, item);
            });
            if (!permitAll) {
                availableCount -= runNum;
            }
        }
        if (!isWaiting) {
            isWaiting = true;
            trigger(reset);
        }
    }

    function reset() {
        isWaiting = false;
        availableCount = limit;
        runQueue();
    }

    function toggle() {
        permitAll = !permitAll;
        runQueue();
    }
    function flush() {
        var argsToRun = argQueues.splice(0, argQueues.length);
        argsToRun.forEach((item) => {
            fn.apply(context, item);
        });
    }
    var queued = function () {
        var args = arguments;
        argQueues.push(args);
        context = this;
        runQueue();
    };
    flushTrigger?.(flush);
    toggleTrigger?.(toggle);
    Object.defineProperty(queued, "status", {
        get: () => ({
            queuedCount: argQueues.length,
            limit: limit,
            availableCount: availableCount,
            isWaiting: isWaiting,
            permitAll: permitAll,
        }),
    });
    return queued;
}

function queueAsync(tasks, limit, onProgress) {
    return new Promise((resolve) => {
        var result = [];
        if (!tasks.length) resolve(result);
        var index = 0;
        var numDone = 0;
        function checkDone(doneIndex, ret) {
            onProgress?.(numDone, tasks.length, doneIndex, ret);
            if (numDone === tasks.length) {
                resolve(result);
                return;
            }
            if (index < tasks.length) {
                _run();
            }
        }
        function _run() {
            const currIndex = index++;
            const task = tasks[currIndex];
            onProgress?.(numDone, tasks.length, currIndex);
            task()
                .then((val) => {
                    numDone++;
                    result[currIndex] = { val };
                    checkDone(currIndex, { val });
                })
                .catch((err) => {
                    numDone++;
                    result[currIndex] = { err };
                    checkDone(currIndex, { err });
                });
        }
        for (var i = 0; i < tasks.length && i < limit; i++) {
            _run();
        }
    });
}

module.exports = {
    queue,
    queueAsync,
};
